/**
  ETFDAQ Project
  \class TACAENCard
  \brief A VME card produced by CAEN Corp. from Italy
  \author SUN Yazhou, asia.rabbit@163.com
  \since 2024-08-13
  \date 2024-08-15 last modified

  \copyright Copyright (c) 2024 IMP-CAS with LGPLv3 LICENSE
*/

#include <iostream>
#include "TACAENCard.h"
#include "TAController.h"
#include "TAMsg.h"
#include "caen_reg.h"

using std::cout;
using std::endl;

// the default setting is for fNCh = 32
TACAENCard::TACAENCard(TAController *ctrl, const TAYaml &c) : TACard(ctrl, c){
  fMAX_EVCTR = 0xffffff - 100;
} // end ctor

TACAENCard::~TACAENCard(){}

void TACAENCard::Initialize(){
  Config(); // config the card according to user yaml input
  SoftwareReset(); // SR, clear data and most of other config registers
  SlotInit(); // set slot and crate ids
  Identify(); // read card rom for manufacturer info
  // configure interrupt //
  if(fIRQLevel) ConfigIRQ(fIRQLevel, fIRQVector, fEventTrigger);
  // usually enabled by default, but the official manual says they are undefined
  if(fChOn.size() == fNCh){
    for(size_t i = 0; i < fNCh; i++) SetChOn(i, fChOn[i]);
  } else SetChOn(-1); // enable all chs (-1 means all the channels)

  // threshold by default is 0
  if(fThres.size() == fNCh){
    for(size_t i = 0; i < fNCh; i++) SetThreshold(i, fThres[i]);
  } else SetThreshold(-1, 0);

  if(fIped && ECardPurpose::QDC == fCardPurpose) SetIped(fIped);

  if(fIsEmptyEnable) EnableEmpty(); // disabled by default
  // small: LSB threshold = LSB value * 2
  if(fIsSmallThreshold) UsingSmallThre(); // default is large threshold step(*32)
  if(fIsKeepUnderflow) KeepUnderflow();
  if(fIsKeepOverflow) KeepOverflow();

  // event counter increments on all trigs (true) or only accepted trigs (false)
  SetAllTrigger(false);

  // problematic: return berr, and the writing takes no effect -- Aug31, 2024
  // SetReadOneEvent(); // only read one event and return
} // end member function Initialize

// read card info (e.g. serialNo.) from its rom, and assign slot & crate Id
void TACAENCard::Identify(){
  fCardType = RegReadA32D16(caenrom::BoardIdMSB) << 16 |
    RegReadA32D16(caenrom::BoardId) << 8 | RegReadA32D16(caenrom::BoardIdLSB);
  fSerialNo = RegReadA32D16(caenrom::SerialMSB) << 8 | RegReadA32D16(caenrom::SerialLSB);
  fHardWareRev = RegReadA32D16(caenrom::Revision);
  fFirmWareRev = RegReadA32D16(caenreg::FirmwareRev);
  fVersion = RegReadA32D16(caenrom::Version);
} // end member function Identify

// write slot id and crate id
void TACAENCard::SlotInit(){
  // cout << "bitset2: "; // DEBUG
  // TAMsg::Print16(RegReadA32D16(caenreg::BitSet2)); // DEBUG

  if(RegReadA32D16(caenreg::Status1) & caenmask::AMNESIA){
    RegWriteA32D16(caenreg::GeoAddress, fSlot);
    RegWriteA32D16(caenreg::CrateSelect, fCrate);
  } // end if
  else{
    TAMsg::Warn("TACAENCard", "SlotInit: AMNESIA is 0, indicating geo not writable,"
      " and can only be picked from the JAUX connector");
    printf("Press ENTER to continue...");
    getchar();
  } // end if
} // end member function SlotInit

// enable empty event recording
void TACAENCard::EnableEmpty() const{
  RegWriteA32D16(caenreg::BitSet2, caenmask::EMPTYEN);
} // end member function EnableEmpty

void TACAENCard::EnableBerr() const{
  RegWriteA32D16(caenreg::Control1,
    RegReadA32D16(caenreg::Control1) | caenmask::BERR_EN);
} // end member function EnableBerr

// level 1~7, 0 will disable the IRQ generation
// vector 0x00~0xFF, requested by vme ctrler to identify the interrupter
// nEv: trigger will be generated after nof buffered events reaches nEv
void TACAENCard::ConfigIRQ(int level, int vector, int nEv) const{
  RegWriteA32D16(caenreg::InterruptLevel, level & 0x7); // 3 bits
  if(!level) return; // IRQ is disabled
  RegWriteA32D16(caenreg::InterruptVector, vector & 0xFF); // 8 bits
  RegWriteA32D16(caenreg::EventTrigger, nEv & 0x1F); // 5 bits
} // end member function ConfigIRQ

// true if buffered events are enough
bool TACAENCard::DataReady() const{
  return RegReadA32D16(caenreg::Status1) & caenmask::DRDY;
} // end member function DataReady

int TACAENCard::ReadData(uint32_t *buf){
  ReadEventCounter(); // read the event counter
  int len = 0;
  for(int i = fDNEv; i--;) len += ReadEvent(buf + len);
  return len;
} // end member function ReadData

// \retval nof words read
int TACAENCard::ReadEvent(uint32_t *buf){
  buf[0] = RegReadA32D32(caenreg::Buffer);
  const int nch = buf[0]>>8 & 0x3F; // nof chs stored in MEB (multi-event buffer)
  // NOTE that the read pointer has automatically incremented by 1 below
  // (namely, one doesn't have to user caenreg::Buffer+4 in the line below)
  // which is ensured to be done by the hardware mechanism of the card
  // (of course provided that this feature is not deliberately user-disabled)
  const int nget = RegReadA32BLT32(caenreg::Buffer, buf+1, nch+1); // 1: trailer
  if(fDebug) PrintEvent(buf);
  return nget + 1;
} // end member function ReadEvent

// clear the modules multi-event buffers
// NOTE that this method clears the data, the write and read pointers,
// the event counter [and e.g., the peak sections (discharges the capacitor C1) for ADCs]
void TACAENCard::ClearData(){
  RegWriteA32D16(caenreg::BitSet2, caenmask::CLRDATA); // set CLEAR_DATA bit
  RegWriteA32D16(caenreg::BitClear2, caenmask::CLRDATA); // clear CLEAR_DATA bit
  if(fEventCounter > fMAX_EVCTR) ClearEventCounter();
} // end member function ClearData

// event counter increments on all trigs (true) or only accepted trigs (false)
void TACAENCard::SetAllTrigger(bool opt) const{
  if(opt) RegWriteA32D16(caenreg::BitSet2, caenmask::ALLTRIG);
  else RegWriteA32D16(caenreg::BitClear2, caenmask::ALLTRIG);
} // end member function ClearData

// the following 2 methods enable or disable certain ch(s)
// by set the KILL bit of the ch threshold to 1
// ch starts from 0, -1 means all channels
// default of threshold (0xff) usually is thre=0 (i.e. thre and killbit are both 0)
void TACAENCard::SetChOn(int ch, bool isOn) const{
  if(ch < 0) for(int i = fNCh; i--;) SetChOn(i);
  else{
    const uint16_t addr = caenreg::Thresholds + ch * fThreAddrIncre;
    if(isOn) RegWriteA32D16(addr, RegReadA32D16(addr) & ~caenmask::KILLBIT);
    else RegWriteA32D16(addr, RegReadA32D16(addr) | caenmask::KILLBIT);
  } // end else
} // end member function SetChOn
// ch starts from 0, -1 means all channels
// only the first 8 bits in thre are valid by default
// kill bit in thre: 1 will kill; while 0 takes no effect, the original killbit
// in the register is preserved
void TACAENCard::SetThreshold(int ch, uint16_t thre) const{
  if(ch < 0) for(int i = fNCh; i--;) SetThreshold(i, thre);
  else{
    const uint16_t addr = caenreg::Thresholds + ch * fThreAddrIncre;
    RegWriteA32D16(addr, (RegReadA32D16(addr) & caenmask::KILLBIT) | (thre & 0xFF));
  } // end else
} // end member function SetThreshold

// set pedestal current for qdcs
void TACAENCard::SetIped(int iped) const{
  RegWriteA32D16(caenreg::Iped, iped & 0xff);
} // end member function SetIped

// true: thre*2; false(default): thre*32
void TACAENCard::UsingSmallThre(bool opt) const{
  if(opt) RegWriteA32D16(caenreg::BitSet2, caenmask::STEPTH);
  else RegWriteA32D16(caenreg::BitClear2, caenmask::STEPTH);
} // end member function UsingSmallThre

// overflow and zero suppressions
void TACAENCard::KeepUnderflow(bool opt) const{
  if(opt) RegWriteA32D16(caenreg::BitSet2, caenmask::LOWTHREEN); // true: keep underflow data
  else RegWriteA32D16(caenreg::BitClear2, caenmask::LOWTHREEN); // false: discard underflow
} // end member function KeepUnderflow
void TACAENCard::KeepOverflow(bool opt) const{
  if(opt) RegWriteA32D16(caenreg::BitSet2, caenmask::OVERANGEEN); // true: keep overflow data
  else RegWriteA32D16(caenreg::BitClear2, caenmask::OVERANGEEN); // false: discard overflow
} // end member function KeepOverflow

// data reset(DR), software reset(SR) and hardware reset(HR) are different
// Please refer to the register map to check what they resets respectively
// usually DR only resets data, ev counters and read/write pointers
// SR is DR plus reset of most of the controlling registers
// HR is SR plus all the resetable registers
void TACAENCard::SoftwareReset(){
  RegWriteA32D16(caenreg::BitSet1, caenmask::SOFTRESET);
  RegWriteA32D16(caenreg::BitClear1, caenmask::SOFTRESET);
} // end member function SoftwareReset

// BLKEND: let the card send all data until the first EOB (end of event)
// then it sends no data. It is realized via setting the Control1 register
// BERR ENABLE: The card generates a BERR error to finish the block transfer
void TACAENCard::SetReadOneEvent() const{
  EnableBerr();
  RegWriteA32D16(caenreg::Control1,
    RegReadA32D16(caenreg::Control1) | caenmask::BLKEND);
} // end member function SetReadOneEvent

// return the event counter register
void TACAENCard::ReadEventCounter(){
  fDNEv = RegReadA32D16(caenreg::EventCounterLow) + 1; // 0~15
  // fEventCounter =
  //   RegReadA32D16(caenreg::EventCounterLow) // 0~15
  //   // + ((RegReadA32D16(caenreg::EventCounterHigh) & 0xff) << 16) // 0~7
  // ;
  fEventCounter += fDNEv;
  if(fDebug){
    cout << "\n\033[1m----- Event Counter Print -----\033[0m" << endl;
    TellYouWhoIAm();
    cout << "total: " << fEventCounter;
    cout << ", nof buffered events: " << fDNEv << endl;
    cout << "ev_ctr_High: ";
    cout << RegReadA32D16(caenreg::EventCounterHigh);
    cout << ", \tev_ctr_low: " << RegReadA32D16(caenreg::EventCounterLow) << endl;
    cout << "\033[1m----- BYE -----\033[0m" << endl;
  } // end if
  if(fDNEv > 1000){
    TellYouWhoIAm();
    TAMsg::Error("TACAENCard", "ReadData: nof buffered events too large: %d", fDNEv);
  } // end if
} // end member function ReadEventCounter

void TACAENCard::ClearEventCounter(){
  RegWriteA32D16(caenreg::EventCounterReset, 0x0); // clear the event counter
  fDNEv = 0;
  // because the event counter reg starts at 0
  fEventCounter = 0; // and fEvCtr should always be 1 less than the evCtr reg
} // end member function ClearEventCounter

void TACAENCard::Print() const{
  TACard::Print();
  if(fThres.size()){
    // print thresholds and the killbit
    cout << "___ Thresholds for all the " << fNCh << " channels ___" << endl;
    cout << "ch \tkill \tthre" << endl;
    for(size_t i = 0; i < fNCh; i++){
      const uint16_t t = RegReadA32D16(caenreg::Thresholds + i * fThreAddrIncre);
      printf("%02zu \t%s \t%u\n", i, bool(t & caenmask::KILLBIT) ? "true" : "false",
        t & 0xFF);
    } // end for over i
  } // end if
} // end member function Print

// print one event
void TACAENCard::PrintEvent(const uint32_t *ev){
  cout << "\033[32;1m-------- Event Print Begin -------- \033[0m" << endl;
  printf("\033[36;1m------ slot %d: %s ------\033[0m\n", fSlot, fCardName.data());
  const uint32_t header = ev[0];
  const int slot = header>>27 & 0x1F; // [27,31]: slot number
  const int nch = header>>8 & 0x3F; // nof chs stored in MEB (multi-event buffer)
  const int crateId = header>>16 & 0xFF;
  static const char *pos[] = {"data", "UDF", "header", "UDF", "trailer", "err",
    "err", "err", "err", "err", "err", "err"};
  printf("%s, crateId: %d, slot: %d, nch: %d, \n",
    pos[header>>24 & 0x7], crateId, slot, nch);
  for(int i = 1; i <= nch; i++){
    const uint32_t data = ev[i];
    const int slot = data>>27 & 0x1F;
    const int chid = data>>fChIdBit0 & fChIdMask;
    const bool ov = data>>12 & 0x1;
    const bool un = data>>13 & 0x1;
    const int adc = data & 0xFFF;
    printf("%s, slot: %d, chid: %02d, ov: %d, un: %d, adc: %u\n",
      pos[data>>24 & 0x7], slot, chid, ov, un, adc);
  } // end for over i
  const uint32_t trailer = ev[nch+1];
  printf("%s, slot: %d, event counter: %d\n",
    pos[trailer>>24 & 0x7], trailer>>27 & 0x1F, trailer & 0xFFFFFF);
  fflush(stdout);
  cout << "\033[32;1m-------- Event Print BYE -------- \033[0m" << endl;
} // end member function PrintEvent
